package com.coremedia.iso.boxes;

import com.coremedia.iso.IsoTypeReader;
import com.coremedia.iso.IsoTypeWriter;
import com.googlecode.mp4parser.AbstractFullBox;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.googlecode.mp4parser.util.CastUtils.l2i;

/**
 * <pre>
 * aligned(8) class CompositionOffsetBox
 * extends FullBox(‘ctts’, version = 0, 0) {
 *  unsigned int(32) entry_count;
 *  int i;
 *  if (version==0) {
 *   for (i=0; i < entry_count; i++) {
 *    unsigned int(32) sample_count;
 *    unsigned int(32) sample_offset;
 *   }
 *  }
 *  else if (version == 1) {
 *   for (i=0; i < entry_count; i++) {
 *    unsigned int(32) sample_count;
 *    signed int(32) sample_offset;
 *   }
 *  }
 * }
 * </pre>
 * <p/>
 * This box provides the offset between decoding time and composition time. In version 0 of this box
 * the decoding time must be less than the composition time, and the offsets are expressed as
 * unsigned numbers such that CT(n) = DT(n) + CTTS(n) where CTTS(n) is the (uncompressed) table
 * entry for sample n.
 * <p/>
 * In version 1 of this box, the composition timeline and the decoding timeline are still derived
 * from each other, but the offsets are signed. It is recommended that for the computed composition
 * timestamps, there is exactly one with the value 0 (zero).
 */
public class CompositionTimeToSample extends AbstractFullBox {
  public static final String TYPE = "ctts";

  List<Entry> entries = Collections.emptyList();

  public CompositionTimeToSample() {
    super(TYPE);
  }

  protected long getContentSize() {
    return 8 + 8 * entries.size();
  }

  public List<Entry> getEntries() {
    return entries;
  }

  public void setEntries(List<Entry> entries) {
    this.entries = entries;
  }

  @Override
  public void _parseDetails(ByteBuffer content) {
    parseVersionAndFlags(content);
    int numberOfEntries = l2i(IsoTypeReader.readUInt32(content));
    entries = new ArrayList<Entry>(numberOfEntries);
    for (int i = 0; i < numberOfEntries; i++) {
      Entry e = new Entry(l2i(IsoTypeReader.readUInt32(content)), content.getInt());
      entries.add(e);
    }
  }

  @Override
  protected void getContent(ByteBuffer byteBuffer) {
    writeVersionAndFlags(byteBuffer);
    IsoTypeWriter.writeUInt32(byteBuffer, entries.size());

    for (Entry entry : entries) {
      IsoTypeWriter.writeUInt32(byteBuffer, entry.getCount());
      byteBuffer.putInt(entry.getOffset());
    }

  }


  public static class Entry {
    int count;
    int offset;

    public Entry(int count, int offset) {
      this.count = count;
      this.offset = offset;
    }

    public int getCount() {
      return count;
    }

    public int getOffset() {
      return offset;
    }

    public void setCount(int count) {
      this.count = count;
    }

    public void setOffset(int offset) {
      this.offset = offset;
    }

    @Override
    public String toString() {
      return "Entry{" +
          "count=" + count +
          ", offset=" + offset +
          '}';
    }
  }


  /**
   * Decompresses the list of entries and returns the list of composition times.
   *
   * @return decoding time per sample
   */
  public static int[] blowupCompositionTimes(List<CompositionTimeToSample.Entry> entries) {
    long numOfSamples = 0;
    for (CompositionTimeToSample.Entry entry : entries) {
      numOfSamples += entry.getCount();
    }
    assert numOfSamples <= Integer.MAX_VALUE;
    int[] decodingTime = new int[(int) numOfSamples];

    int current = 0;


    for (CompositionTimeToSample.Entry entry : entries) {
      for (int i = 0; i < entry.getCount(); i++) {
        decodingTime[current++] = entry.getOffset();
      }
    }

    return decodingTime;
  }

}
